package com.lzq.mq.nameserver.event.spi.listener;

import com.alibaba.fastjson2.JSON;
import com.lzq.mq.common.coder.TcpMsg;
import com.lzq.mq.common.constants.NameServerConstants;
import com.lzq.mq.common.dto.ServiceRegistryRespDTO;
import com.lzq.mq.common.enums.NameServerResponseCode;
import com.lzq.mq.common.event.Listener;
import com.lzq.mq.nameserver.common.CommonCache;
import com.lzq.mq.nameserver.enums.ReplicationModeEnum;
import com.lzq.mq.nameserver.enums.ReplicationMsgTypeEnum;
import com.lzq.mq.nameserver.event.model.RegistryEvent;
import com.lzq.mq.nameserver.event.model.ReplicationMsgEvent;
import com.lzq.mq.nameserver.store.ServiceInstance;
import com.lzq.mq.nameserver.utils.NameserverUtils;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;

import java.util.UUID;

/**
 * 也可以仅仅通过heartbeat来注册和下线
 * 单独添加注册监听器, 可以精细化操作, 方便实现鉴权, 防止恶意注册
 */
@Slf4j
public class RegisterListener implements Listener<RegistryEvent> {

    /**
     * 通过密码, 用户鉴权注册
     * @param event 事件对象
     */
    @Override
    public void onReceive(RegistryEvent event) throws IllegalAccessException {

        boolean isVerify = NameserverUtils.isVerify(event.getUser(), event.getPassword());

        ChannelHandlerContext channelHandlerContext = event.getChannelHandlerContext();
        // 生成返回的消息, 用于异步转同步
        ServiceRegistryRespDTO respDTO = new ServiceRegistryRespDTO();
        respDTO.setMsgId(event.getMsgId());
        byte[] respJsonBytes = JSON.toJSONBytes(respDTO);
        // 密码用户名错误
        if (!isVerify) {

            TcpMsg tcpMsg = new TcpMsg(
                    NameServerResponseCode.ERROR_USER_OR_PASSWORD.getCode(),
                    respJsonBytes
            );

            channelHandlerContext.writeAndFlush(tcpMsg);    // 写消息并刷新
            channelHandlerContext.close();      // 关闭通道

            throw new IllegalAccessException("用户名或密码错误");
        }

        // 处理登录请求 为连接设置唯一的标识
        /*
            ctx.attr(key) 会返回一个 Attribute<T>，
            用于在“通道（Channel）级别”存放/读取与该连接相关的自定义属性（比如：用户 id、认证状态、会话信息、心跳时间戳等）。
            Attribute 是一个轻量的、可原子操作的容器，可用于简单并发场景的原子更新。

            什么事通道Channel?
            Channel 是对一个可进行 I/O 操作的实体的抽象，通常对应一个底层的网络连接或套接字（TCP 连接、UDP 端点、本地管道等）。
            它封装了读写、绑定、连接、关闭等 I/O 操作并暴露连接的元数据（地址、id 等）。
         */
        AttributeKey<String> reqId = AttributeKey.valueOf(NameServerConstants.NAMESERVER_CHANNEL_ID); // valueOf和newAttributeKey方法都可以创建AttributeKey
        String id = event.getIp() + ":" + event.getPort();
        channelHandlerContext.channel().attr(reqId).set(id);

        // 获取构建服务实例需要的参数 -> 改用直接通过event参数传递
        long timeStamp = System.currentTimeMillis();

        // 构建服务实例
        ServiceInstance serviceInstance = ServiceInstance.builder()
                .registryType(event.getRegistryType())
                .ip(event.getIp())
                .port(event.getPort())
                .firstRegistryTime(timeStamp)
                .lastHeartBeatTime(timeStamp)
                .attrs(event.getAttrs())
                .build();

        CommonCache.getServiceInstanceManager().put(serviceInstance);

        ReplicationModeEnum replicationModeEnum = ReplicationModeEnum.of(CommonCache.getNameserverProperties().getReplicationMode());
        if (replicationModeEnum != null && replicationModeEnum.equals(ReplicationModeEnum.STANDALONE)) {
            //单机架构，不做复制处理， 直接ack
            TcpMsg tcpMsg = new TcpMsg(
                    NameServerResponseCode.REGISTRY_SERVICE_SUCCESS.getCode(),
                    respJsonBytes
            );
            channelHandlerContext.writeAndFlush(tcpMsg);
            log.info("服务注册: {}", serviceInstance);
            return;
        }

        // 如果当前是主从同步模式, 并且当前节点是主节点, 还需要向同步队列中添加元素
        ReplicationMsgEvent replicationMsgEvent = new ReplicationMsgEvent();
        replicationMsgEvent.setServiceInstance(serviceInstance);
        replicationMsgEvent.setMsgId(UUID.randomUUID().toString());
        replicationMsgEvent.setChannelHandlerContext(event.getChannelHandlerContext());
        replicationMsgEvent.setType(ReplicationMsgTypeEnum.REGISTRY.getCode());
        CommonCache.getReplicationMsgQueueManager().put(replicationMsgEvent);       // 提交同步任务

        // 回复客户端, 注册成功
        log.info("服务注册成功: {}", serviceInstance);
    }
}
